#ifndef	__CHAR_UTILITY_H__
#define	__CHAR_UTILITY_H__

namespace BFX
{

/* static */
class BFX_API CharUnicodeInfo
{
public:
	enum UnicodeCategory
	{
        UppercaseLetter = 0,
        LowercaseLetter = 1,
        TitlecaseLetter = 2,
        ModifierLetter = 3,
        OtherLetter = 4,
        NonSpacingMark = 5,
        SpacingCombiningMark = 6,
        EnclosingMark = 7,
        DecimalDigitNumber = 8,
        LetterNumber = 9,
        OtherNumber = 10,
        SpaceSeparator = 11,
        LineSeparator = 12,
        ParagraphSeparator = 13,
        Control = 14,
        Format = 15,
        Surrogate = 16,
        PrivateUse = 17,
        ConnectorPunctuation = 18,
        DashPunctuation = 19,
        OpenPunctuation = 20,
        ClosePunctuation = 21,
        InitialQuotePunctuation = 22,
        FinalQuotePunctuation = 23,
        OtherPunctuation = 24,
        MathSymbol = 25,
        CurrencySymbol = 26,
        ModifierSymbol = 27,
        OtherSymbol = 28,
        OtherNotAssigned = 29
	};

private:
	struct UnicodeDataHeader
	{
		// Fields
		// Offset(0)
		UCHAR TableName[16];
		// Offset(0x20)
		UINT16 Version[4];
		// Offset(0x28)
		UINT32 OffsetToCategoriesIndex;
		// Offset(0x2c)
		UINT32 OffsetToCategoriesValue;
		// Offset(0x30)
		UINT32 OffsetToNumbericIndex;
		// Offset(0x34)
		UINT32 OffsetToDigitValue;
		// Offset(0x38)
		UINT32 OffsetToNumbericValue;
	};

    // NOTE: It's important to specify pack size here, since the size of the structure is 2 bytes.  Otherwise,
    // the default pack size will be 4.
	#pragma pack(push, 2)
	struct DigitValues
	{
		INT8 decimalDigit;
		INT8 digit;
	};
	#pragma pack(pop)

	typedef struct {
		UINT32 OffsetToUppercasing;
		UINT32 OffsetToLowercasing;
	} CasingPlaneOffset;

	struct CasingDataHeader
	{
		// Offset(0)
		UCHAR TableName[16];
		// Offset(0x20)
		UINT16 Version[4];
		// Offset(0x28)
		UINT32 OffsetToUpperCasingTable; // DWORD
		// Offset(0x2c)
		UINT32 OffsetToLowerCasingTable; // DWORD
		// Offset(0x30)
		UINT32 OffsetToTitleCaseTable; // DWORD
		// Offset(0x34)
		CasingPlaneOffset PlaneOffset[16];
		// Each plane has DWORD offset for uppercase  and DWORD offset for lowercase.
		// 0xb4 = 0x34 + 8*16
		// Offset(0xb4)
		UINT16 ExceptionCount;
		// Offset(0xb6)
		UINT16 ExceptionLangId;
	};

public:
	static const int W_UNICODE_PLANE00_END		= 0x00ffff;
	// The starting codepoint for Unicode plane 1.  Plane 1 contains 0x010000 ~ 0x01ffff.
	static const int W_UNICODE_PLANE01_START	= 0x10000;
	// The end codepoint for Unicode plane 16.  This is the maximum code point value allowed for Unicode.
	// Plane 16 contains 0x100000 ~ 0x10ffff.
	static const int W_UNICODE_PLANE16_END		= 0x10ffff;

	static const int W_HIGH_SURROGATE_START		= 0x00d800;	// '\ud800';
	static const int W_HIGH_SURROGATE_END		= 0x00dbff;	// '\udbff';
	static const int W_LOW_SURROGATE_START		= 0x00dc00;	// '\udc00';
	static const int W_LOW_SURROGATE_END		= 0x00dfff;	// '\udfff';

	static bool IsSurrogate(UCHAR c)
	{
		return (c >= W_HIGH_SURROGATE_START && c <= W_LOW_SURROGATE_END);
	}
	static bool IsHighSurrogate(UCHAR c)
	{
		return ((c >= W_HIGH_SURROGATE_START) && (c <= W_HIGH_SURROGATE_END));
	} 
	static bool IsLowSurrogate(UCHAR c)
	{
		return ((c >= W_LOW_SURROGATE_START) && (c <= W_LOW_SURROGATE_END));
	}

	static int ConvertToUtf32(UCHAR highSurrogate, UCHAR lowSurrogate)
	{
		BFX_REQUIRE1(IsHighSurrogate(highSurrogate), "A valid high surrogate character is between 0xd800 and 0xdbff, inclusive.");
		BFX_REQUIRE1(IsLowSurrogate(lowSurrogate), "A valid low surrogate character is between 0xdc00 and 0xdfff, inclusive.");

		return (((highSurrogate - W_HIGH_SURROGATE_START) * 0x400) + (lowSurrogate - W_LOW_SURROGATE_START) + W_UNICODE_PLANE01_START);
	}

	// Returns the Unicode Category property for the character ch.
	// Note that this API will return values for D800-DF00 surrogate halves.
	static UnicodeCategory GetUnicodeCategory(UCHAR ch, int nOffset = 0);

	// Determines if the given character is a white space character.
	static bool IsWhiteSpace(UCHAR ch);

	static UCHAR ToLowercase(UCHAR ch)
	{
		return ChangeCaseChar(false, ch);
	}
	static UCHAR ToUppercase(UCHAR ch)
	{
		return ChangeCaseChar(true, ch);
	}
	static UCHAR ChangeCaseChar(bool bIsToUpper, UCHAR ch);

	static void ToLowercase(const UCHAR* pchSource, UCHAR* pchTarget, int nStrLen)
	{
		ChangeCaseString(false, pchSource, pchTarget, nStrLen);
	}
	static void ToUppercase(const UCHAR* pchSource, UCHAR* pchTarget, int nStrLen)
	{
		ChangeCaseString(true, pchSource, pchTarget, nStrLen);
	}
	static void ChangeCaseString(bool bIsToUpper, const UCHAR* pchSource, UCHAR* pchTarget, int nStrLen);

private:
	static void StaticInitialize();

	static bool s_bInitialized;
	static SpinLock	s_initLock;

	static const BYTE	s_pDataTable[];
	static UINT16*	s_pCategoryLevel1Index;
	static BYTE*	s_pCategoriesValue;
	static UINT16*	s_pNumericLevel1Index;
	static BYTE*	s_pNumericValues;
	static DigitValues*	s_pDigitValues;

	static const BYTE	s_pCasingTable[];
	static BYTE*	s_pUpperCaseTable;
	static BYTE*	s_pLowerCaseTable;
	static BYTE*	s_pTitleCasingTable;
	static CasingPlaneOffset*	s_pPlaneOffset;
};

}	//	namespace BFX

#endif	//	__CHAR_UTILITY_H__
